home *** CD-ROM | disk | FTP | other *** search
/ The Original Shareware 1.1 / The Original Shareware (WeMake CDs)(Volume 1.1)(CDs, Inc)(1993).iso / 32 / tictacai.zip / TICTAC.C < prev    next >
C/C++ Source or Header  |  1990-06-17  |  24KB  |  910 lines

  1.  
  2. /*
  3.  * TICTAC.C
  4.  *
  5.  * Copyright (C) 1990 by Aaron L. Brenner
  6.  *
  7.  * A game of Tic-Tac-Toe that learns as it goes along.
  8.  * The knowledge is stored as a linked list of board configurations (see
  9.  * BOARDLIST definition below), each of which points to a linked list of
  10.  * move/weight pairs (see PLAYLIST definition below).
  11.  *
  12.  * Revision history:
  13.  * 1.00        06/12/90    ALB        Created.
  14.  */
  15.  
  16. #include <stdio.h>
  17. #include <limits.h>
  18. #include <malloc.h>
  19.  
  20. #include "tictac.h"
  21.  
  22.  
  23. #define    mem_err(x)        err_exit("memory allocation", x)
  24. #define    eof_err(x)        err_exit("unexpected EOF", x)
  25. #define    write_err(x)    err_exit("file write", x)
  26.  
  27.  
  28. char    memory_name[78] = "tictac.mem";        /* File name for "memory"        */
  29.  
  30.  
  31. /*
  32.  * Define the structure of each recorded move
  33.  */
  34. typedef    struct    playlist {
  35.     unsigned    char    pl_move;            /* Move this corresponds to        */
  36.                 char    pl_weight;            /* "Weight" for this move        */
  37.     struct    playlist    *pl_next;            /* Next one in the list            */
  38. } PLAYLIST;
  39.  
  40.  
  41. /*
  42.  * Define the structure of each "remembered" board
  43.  */
  44. typedef    struct    boardlist {
  45. /*    unsigned    char    bl_board[9];        /* The board positions            */
  46.     unsigned    int        bl_board;            /* Encoded game board            */
  47.     unsigned    char    bl_count;            /* Count of play list elements    */
  48.     PLAYLIST            *bl_plays;            /* List of plays made here        */
  49.     struct    boardlist    *bl_next;            /* Next board in the list        */
  50. } BOARDLIST;
  51.  
  52. BOARDLIST    *board_memory = NULL;            /* Head of list of boards        */
  53.  
  54. PLAYLIST    *game_moves[9];                    /* Moves we made this game        */
  55.  
  56. int            move_count;                        /* Count of elements in above    */
  57.  
  58. int            have_mouse;                        /* Set during initialization    */
  59. unsigned    char    play_board[9];            /* Current game board            */
  60. char        *prompt,                        /* Set at init, depending on    */
  61.             *yes_no_prompt;                    /*  presence of mouse            */
  62. char        bad_move[] = "Invalid move";
  63.  
  64. unsigned    games_played = 0,
  65.             games_won = 0,
  66.             games_lost = 0,
  67.             games_drawn = 0;
  68.  
  69.  
  70. /*
  71.  * This array serves 2 purpose:
  72.  * 1. Determine whether or not the mouse cursor is in a square when clicked.
  73.  * 2. Identify where to put the markers on screen.
  74.  *
  75.  * Since this is also used for the mouse, all the screen coordinates are
  76.  * 0-based.
  77.  */
  78. REGION        screen_regions[] = {
  79.     {14, 20, 18, 31},
  80.     {14, 34, 18, 45},
  81.     {14, 48, 18, 59},
  82.     { 8, 20, 12, 31},
  83.     { 8, 34, 12, 45},
  84.     { 8, 48, 12, 59},
  85.     { 2, 20,  6, 31},
  86.     { 2, 34,  6, 45},
  87.     { 2, 48,  6, 59}
  88. };
  89.  
  90. #define    NUM_REGIONS    (sizeof(screen_regions) / sizeof(REGION))
  91.  
  92. /*
  93.  * These arrays define how the markers look.
  94.  *
  95.  * 0 means space
  96.  * 1 means lower-half block
  97.  * 2 means upper-half block
  98.  * 3 means full block
  99.  */
  100. char    omarker[32] = {
  101.     0, 1, 1, 1, 1, 1, 1, 0, 3, 0, 0, 0, 0, 0, 0, 3,
  102.     3, 0, 0, 0, 0, 0, 0, 3, 2, 1, 1, 1, 1, 1, 1, 2
  103. };
  104.  
  105. char    xmarker[32] = {
  106.     0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 1, 2, 0,
  107.     0, 0, 0, 1, 2, 1, 0, 0, 0, 1, 2, 0, 0, 0, 2, 1
  108. };
  109.  
  110.  
  111. /*
  112.  * Error exit routine
  113.  */
  114. void    err_exit(char *msg, char *locus)
  115. {
  116.     cls();                                    /* Clear the screen                */
  117.     fputs("Fatal ", stderr);                /* Tell them it's a fatal error    */
  118.     fputs(msg, stderr);                        /* Spit out the message            */
  119.     fputs(" error in/about ", stderr);        /* Say about where it is        */
  120.     fputs(locus, stderr);
  121.     fputc('\n', stderr);                    /* New line                        */
  122.     exit(1);                                /* Get the hell out of Dodge    */
  123. }
  124.  
  125.  
  126. /*
  127.  * Load any remembered boards from disk
  128.  *
  129.  * The memory file is organized as follows:
  130.  *        word        count of boards
  131.  *        <boards>
  132.  *        <move/weight pairs>
  133.  * The count of pairs for each board is stored with the board.
  134.  */
  135. void    load_boards()
  136. {
  137.     FILE        *memfile;
  138.     unsigned    bcount,
  139.                 pcount,
  140.                 count2;
  141.     BOARDLIST    *tboards,                    /* Ptr for allocating            */
  142.                 *tbdtail;                    /* Keeps track of the end of    */
  143.                                             /*  the board list so we don't    */
  144.                                             /*  have to chain through the    */
  145.                                             /*  entire list each time        */
  146.     PLAYLIST    *tplays,                    /* Ptr for allocating            */
  147.                 *tplaystail;                /* Same purpose as tbdtail, but    */
  148.                                             /*  for the move list            */
  149.  
  150.     /*
  151.      * Try to open the memory file. If it fails, no big deal, just no memory
  152.      */
  153.     if ((memfile = fopen(memory_name, "rb")) != NULL) {
  154.  
  155.         /*
  156.          * Make the file buffer bigger than 1 sector so that the I/O goes
  157.          * faster
  158.          */
  159.         setvbuf(memfile, NULL, _IOFBF, 8192);
  160.  
  161.         /*
  162.          * Read in the count of boards. If that fails, die.
  163.          */
  164.         if ((bcount = (unsigned)getw(memfile)) == EOF)
  165.             eof_err("load_boards[board count]");
  166.         tbdtail = NULL;                        /* Set up the tail ptr            */
  167.  
  168.         /*
  169.          * Load in each board in the file
  170.          */
  171.         for (count2 = 0; count2 < bcount; count2++) {
  172.  
  173.             /*
  174.              * Allocate a structure for this board. If it fails, die.
  175.              */
  176.             if ((tboards = malloc(sizeof(BOARDLIST))) == NULL)
  177.                 mem_err("load_boards[board list]");
  178.             if (tbdtail == NULL)            /* If none yet,                    */
  179.                 board_memory = tboards;        /* Set the list head            */
  180.             else                            /* But if we already have some,    */
  181.                 tbdtail->bl_next = tboards;    /*  set our tail ptr's ptr        */
  182.             tbdtail = tboards;                /* Point to the last one        */
  183.             tboards->bl_plays = NULL;        /* Set up ptr to play list        */
  184.             tboards->bl_next = NULL;        /* Keep list terminated            */
  185.             /*
  186.              * Read in the board itself. If it fails, die.
  187.              */
  188.             if (fread(&(tboards->bl_board), 1, sizeof(tboards->bl_board) +
  189.                         sizeof(char), memfile) != (sizeof(tboards->bl_board) +
  190.                                                             sizeof(char)))
  191.                 eof_err("load_boards[board data]");
  192.         }
  193.  
  194.         /*
  195.          * We've read in all the board configurations. Now, we have to go
  196.          * through each board, reading in the plays for that board.
  197.          */
  198.         for (tboards = board_memory; tboards != NULL;
  199.                                                 tboards = tboards->bl_next) {
  200.             tplaystail = NULL;                /* Set up list tail ptr            */
  201.             pcount = tboards->bl_count;        /* Get play list count            */
  202.             for (count2 = 0; count2 < pcount; count2++) {
  203.                 if ((tplays = malloc(sizeof(PLAYLIST))) == NULL)
  204.                     mem_err("load_boards[play list]");
  205.                 if (tplaystail == NULL)        /* If none yet,                    */
  206.                     tboards->bl_plays = tplays;    /* Set ptr in board entry    */
  207.                 else                        /* Otherwise keep list right    */
  208.                     tplaystail->pl_next = tplays;
  209.                 tplaystail = tplays;
  210.                 tplays->pl_next = NULL;        /* Keep list terminated            */
  211.  
  212.                 /*
  213.                  * Read in a move/weight pair. If it fails, die.
  214.                  */
  215.                 if (fread(&(tplays->pl_move), 1, sizeof(unsigned char) +
  216.                             sizeof(char), memfile) < (sizeof(unsigned char) +
  217.                                                             sizeof(char)))
  218.                     eof_err("load_boards[play list]");
  219.             }
  220.         }
  221.         fclose(memfile);                    /* Close it                        */
  222.     }
  223. }
  224.  
  225.  
  226. /*
  227.  * One-time-only program initialization.
  228.  *    * Draw the main board
  229.  *    * Initialize the mouse
  230.  *    * Load the memory file
  231.  */
  232. void    initialize()
  233. {
  234.     int        i;
  235.  
  236.     init_screen();                            /* Init the screen package        */
  237.     set_cursor(OFF);                        /* Get rid of that sucker        */
  238.     gotoxy(2, 20);                            /* Draw the frame                */
  239.     out_char(0xc9);
  240.     for (i = 21; i < 61; i++)
  241.         out_char(0xcd);
  242.     out_char(0xbb);
  243.     for (i = 3; i < 20; i++) {
  244.         gotoxy(i, 20);
  245.         out_char(0xba);
  246.         gotoxy(i, 33);
  247.         out_char(0xdb);
  248.         out_char(0xdb);
  249.         gotoxy(i, 47);
  250.         out_char(0xdb);
  251.         out_char(0xdb);
  252.         gotoxy(i, 61);
  253.         out_char(0xba);
  254.     }
  255.     gotoxy(20, 20);
  256.     out_char(0xc8);
  257.     for (i = 21; i < 61; i++)
  258.         out_char(0xcd);
  259.     out_char(0xbc);
  260.     gotoxy(8, 21);
  261.     for (i = 21; i < 61; i++)
  262.         out_char(0xdb);
  263.     gotoxy(14, 21);
  264.     for (i = 21; i < 61; i++)
  265.         out_char(0xdb);
  266.     have_mouse = (mouse_init() != 0);        /* Hook the mouse                */
  267.     if (have_mouse) {
  268.         mouse_bounds(2, 20, 18, 59);
  269.         prompt =
  270.             "Select your move by typing a digit from 1 to 9, or by clicking";
  271.         yes_no_prompt = "[Y or left button, N or right button]";
  272.     } else {
  273.         prompt = "Enter your move by typing a digit from 1 to 9";
  274.         yes_no_prompt = "[Y or N]";
  275.     }
  276.     mouse_on();                                /* Enable mouse processing        */
  277.     mouse_cursor(OFF);                        /* Turn it off                    */
  278.     load_boards();                            /* Load the boards from disk    */
  279. }
  280.  
  281.  
  282. /*
  283.  * Set up the board for a new game
  284.  */
  285. void    init_board()
  286. {
  287.     int        i;
  288.  
  289.     move_count = 0;                            /* Reset game move counter        */
  290.     memset(play_board, 0, sizeof(play_board));    /* Clear the game board        */
  291.  
  292.     /*
  293.      * Go through each square in the board (on screen), and clear it.
  294.      */
  295.     for (i = 0; i < NUM_REGIONS; i++) {
  296.         clear_region(screen_regions[i].ul_row+1, screen_regions[i].ul_col+1,
  297.                      screen_regions[i].lr_row+1, screen_regions[i].lr_col+1);
  298.         gotoxy(screen_regions[i].lr_row + 1, screen_regions[i].lr_col + 1);
  299.         intensity(ON);
  300.         out_char(i + '1');
  301.         intensity(OFF);
  302.     }
  303.     gotoxy(24, 1);
  304.     clear_below();
  305. }
  306.  
  307.  
  308. /*
  309.  * Display a move
  310.  *    who is either 1 (for the player) or 2 (for the computer)
  311.  *    where is a square number (0 to 8)
  312.  */
  313. void    show_move(int who, int where)
  314. {
  315.     int        base_row,
  316.             base_col,
  317.             t_row,
  318.             t_col;
  319.     char    *temp;
  320.  
  321.     if (who == 1)                            /* Figure out which marker        */
  322.         temp = xmarker;
  323.     else
  324.         temp = omarker;
  325.  
  326.     /*
  327.      * Get the base row and column from the region array. Adjust them, since
  328.      * they're 0-based in the array and the screen package expects 1-based
  329.      * coordinates.
  330.      */
  331.     base_row = screen_regions[where].ul_row + 1;
  332.     base_col = screen_regions[where].ul_col + 2;
  333.     for (t_row = 0; t_row < 4; t_row++) {
  334.         gotoxy(base_row + t_row, base_col);
  335.         for (t_col = 0; t_col < 8; t_col++, temp++)
  336.             switch (*temp) {
  337.                 case 0:                        /* Space                        */
  338.                     out_char(' ');
  339.                     break;
  340.  
  341.                 case 1:                        /* Lower half-block                */
  342.                     out_char(0xdc);
  343.                     break;
  344.  
  345.                 case 2:                        /* Upper half-block                */
  346.                     out_char(0xdf);
  347.                     break;
  348.  
  349.                 default:                    /* Full block                    */
  350.                     out_char(0xdb);
  351.             }
  352.     }
  353. }
  354.  
  355.  
  356. /*
  357.  * Get a move from the player
  358.  * Returns 1 if they want to quit; 0 otherwise.
  359.  */
  360. int        player_move()
  361. {
  362.     int        oldf,
  363.             oldb,
  364.             oldi,
  365.             oldk,
  366.             old_state,
  367.             move;
  368.     unsigned    pinput;
  369.  
  370.     do {
  371.  
  372.         /*
  373.          * Clear the last line, and write out the prompt in reverse video
  374.          */
  375.         gotoxy(25, 1);
  376.         clear_eol();
  377.         gotoxy(25, 40 - (strlen(prompt) / 2));
  378.         get_attrib(&oldf, &oldb, &oldi, &oldk);
  379.         foreground(oldb);
  380.         background(oldf);
  381.         out_string(prompt);
  382.         foreground(oldf);
  383.         background(oldb);
  384.         if (have_mouse) {                    /* Turn the mouse cursor on if    */
  385.             mouse_cursor(ON);                /*  there is a mouse, and get    */
  386.             old_state = mouse_buttons();    /*  the current button state    */
  387.         } else
  388.             old_state = 0;                    /* No mouse, so use 0 for start    */
  389.  
  390.         /*
  391.          * Loop, waiting for either a keystroke or a mouse button click (the
  392.          * button goes down then up).
  393.          */
  394.         for (;;) {
  395.  
  396.             /*
  397.              * See if there are any keystrokes to read.
  398.              */
  399.             if ((pinput = tvinkey("1-9^[", "", 0xffff)) != 0xffff) {
  400.                 if (pinput == 27) {            /* Esc quits                    */
  401.                     mouse_cursor(OFF);
  402.                     return (1);
  403.                 }
  404.                 move = pinput - '1';
  405.                 break;
  406.             }
  407.  
  408.             /*
  409.              * Check the mouse buttons. If they've changed AND the new state
  410.              * indicates that a button has gone UP, it means it's been
  411.              * clicked. Handle it.
  412.              */
  413.             if ((pinput = mouse_buttons()) != old_state) {
  414.                 if ((old_state & 2) && !(pinput & 2)) {
  415.                     mouse_cursor(OFF);        /* Right button quits            */
  416.                     return (1);
  417.                 }
  418.                 if ((old_state & 1) && !(pinput & 1)) {
  419.                     if ((move = mouse_in_region(screen_regions, NUM_REGIONS))
  420.                                                                         != -1)
  421.                         break;                /* Left button selects a square    */
  422.                 }
  423.                 old_state = pinput;            /* Save that as the old state    */
  424.             }
  425.         }
  426.         mouse_cursor(OFF);                    /* Turn the mouse cursor off    */
  427.  
  428.         if (play_board[move]) {                /* If they picked one that is    */
  429.                                             /*  already occupied,...        */
  430.             gotoxy(24, 40 - (strlen(bad_move) / 2));
  431.             foreground(oldb);
  432.             background(oldf);
  433.             out_string(bad_move);            /* ..., complain about it        */
  434.             foreground(oldf);
  435.             background(oldb);
  436.         } else {
  437.             gotoxy(24, 1);                    /* Erase any message that might    */
  438.             clear_eol();                    /*  have been displayed earlier    */
  439.         }
  440.     } while (play_board[move]);                /* Loop until they select a        */
  441.                                             /*  square that isn't occupied    */
  442.     play_board[move] = 1;                    /* Mark it as now occupied        */
  443.     show_move(1, move);                        /* Show their marker            */
  444.     return (0);
  445. }
  446.  
  447.  
  448. /*
  449.  * Indicate what the winning positions are by making them blink
  450.  */
  451. void    flash_winner(int index1, int index2, int index3)
  452. {
  453.     blinking(ON);
  454.     show_move(play_board[index1], index1);
  455.     show_move(play_board[index2], index2);
  456.     show_move(play_board[index3], index3);
  457.     blinking(OFF);
  458. }
  459.  
  460.  
  461. /*
  462.  * See if the game is finished. If not, return 0. If it is, return 1 if the
  463.  * player won, or 2 if the machine won. If it's a draw, return 3.
  464.  */
  465. int        game_over()
  466. {
  467.     int        i;
  468.  
  469.     /*
  470.      * The brute force approach is used. Since the board is so small, just
  471.      * check for each possible winning combo.
  472.      */
  473.  
  474.     /*
  475.      * First, check the diagonals
  476.      */
  477.     if (play_board[0] && (play_board[0] == play_board[4]) &&
  478.                                         (play_board[0] == play_board[8])) {
  479.         flash_winner(0, 4, 8);
  480.         return (play_board[0]);
  481.     }
  482.     if (play_board[2] && (play_board[2] == play_board[4]) &&
  483.                                         (play_board[2] == play_board[6])) {
  484.         flash_winner(2, 4, 6);
  485.         return (play_board[2]);
  486.     }
  487.  
  488.     /*
  489.      * Now, check along each row.
  490.      */
  491.     if (play_board[0] && (play_board[0] == play_board[1]) &&
  492.                                         (play_board[0] == play_board[2])) {
  493.         flash_winner(0, 1, 2);
  494.         return (play_board[0]);
  495.     }
  496.     if (play_board[3] && (play_board[3] == play_board[4]) &&
  497.                                         (play_board[3] == play_board[5])) {
  498.         flash_winner(3, 4, 5);
  499.         return (play_board[3]);
  500.     }
  501.     if (play_board[6] && (play_board[6] == play_board[7]) &&
  502.                                         (play_board[6] == play_board[8])) {
  503.         flash_winner(6, 7, 8);
  504.         return (play_board[6]);
  505.     }
  506.  
  507.     /*
  508.      * Lastly, check along the columns.
  509.      */
  510.     if (play_board[0] && (play_board[0] == play_board[3]) &&
  511.                                         (play_board[0] == play_board[6])) {
  512.         flash_winner(0, 3, 6);
  513.         return (play_board[0]);
  514.     }
  515.     if (play_board[1] && (play_board[1] == play_board[4]) &&
  516.                                         (play_board[1] == play_board[7])) {
  517.         flash_winner(1, 4, 7);
  518.         return (play_board[1]);
  519.     }
  520.     if (play_board[2] && (play_board[2] == play_board[5]) &&
  521.                                         (play_board[2] == play_board[8])) {
  522.         flash_winner(2, 5, 8);
  523.         return (play_board[2]);
  524.     }
  525.  
  526.     /*
  527.      * Ok, so nobody has won yet. Now check for a draw.
  528.      */
  529.     for (i = 0; i < 9; i++)
  530.         if (play_board[i] == 0)                /* If at least 1 empty space,    */
  531.             return (0);                        /*  no draw yet                    */
  532.     return (3);                                /* Return that it's a draw        */
  533. }
  534.  
  535.  
  536. /*
  537.  * Find a possible move, given the position.
  538.  */
  539. PLAYLIST    *get_possible(int where)
  540. {
  541.     unsigned    coded_board;                /* Encoded game board            */
  542.     int            i;
  543.     BOARDLIST    *btemp1,
  544.                 *btemp2;
  545.     PLAYLIST    *ptemp1,
  546.                 *ptemp2;
  547.     static    unsigned    powers[9] = {
  548.         3, 9, 27, 81, 243, 729, 2187, 6561, 19683
  549.     };
  550.  
  551.     btemp2 = NULL;
  552.  
  553.     /*
  554.      * Encode the game board for comparison & storage
  555.      */
  556.     coded_board = 0;
  557.     for (i = 0; i < 9; i++)
  558.         coded_board += powers[i] * (unsigned)play_board[i];
  559.  
  560.     /*
  561.      * Search down the list of "remembered" boards, looking for one that
  562.      * matches the current game board.
  563.      */
  564.     for (btemp1 = board_memory; btemp1 != NULL; btemp1 = btemp1->bl_next) {
  565. /*        if (memcmp(btemp1->bl_board, play_board, 9) == 0)
  566. */        if (btemp1->bl_board == coded_board)
  567.             break;
  568.         btemp2 = btemp1;
  569.     }
  570.     if (btemp1 == NULL) {
  571.  
  572.         /*
  573.          * We don't have a record of this board configuration, so we need to
  574.          * add it to the list (that's what btemp2 is for - so we don't have
  575.          * to search for the end again).
  576.          */
  577.         if ((btemp1 = malloc(sizeof(BOARDLIST))) == NULL)
  578.             mem_err("get_possible[board list]");
  579.         if (btemp2 == NULL)                    /* If the list was empty,        */
  580.             board_memory = btemp1;            /* Make this the head            */
  581.         else                                /* Otherwise,                    */
  582.             btemp2->bl_next = btemp1;        /* Just tack it onto the end    */
  583.         btemp1->bl_count = 0;                /* Initialize this one            */
  584.         btemp1->bl_plays = NULL;
  585.         btemp1->bl_next = NULL;
  586. /*        memcpy(btemp1->bl_board, play_board, 9);
  587. */        btemp1->bl_board = coded_board;        /* Store the encoded game board    */
  588.     }
  589.  
  590.     /*
  591.      * At this point, we've either found the correct board, or we've added it
  592.      * to the list. In either case, we now have btemp1 pointing to it. Now,
  593.      * all we have to do is zip along the associated playlist until we either
  594.      * find this move (in which case we return the pointer to it) or we run
  595.      * out of list (so we have to allocate a new one).
  596.      */
  597.     ptemp2 = NULL;
  598.     for (ptemp1 = btemp1->bl_plays; ptemp1 != NULL; ptemp1 = ptemp1->pl_next) {
  599.         if (ptemp1->pl_move == where)
  600.             break;
  601.         ptemp2 = ptemp1;
  602.     }
  603.  
  604.     if (ptemp1 == NULL) {                    /* This move isn't in the list    */
  605.         if ((ptemp1 = malloc(sizeof(PLAYLIST))) == NULL)
  606.             mem_err("get_possible[play list]");
  607.         if (ptemp2 == NULL)                    /* No head of list yet, so        */
  608.             btemp1->bl_plays = ptemp1;        /* Make it the head                */
  609.         else
  610.             ptemp2->pl_next = ptemp1;        /* Just tack it onto the end    */
  611.         ptemp1->pl_next = NULL;                /* Init the new element            */
  612.         ptemp1->pl_weight = 0;
  613.         ptemp1->pl_move = where;
  614.         btemp1->bl_count++;
  615.     }
  616.     return (ptemp1);                        /* Return whatever element        */
  617. }
  618.  
  619.  
  620. /*
  621.  * Generate a move for the "O"s.
  622.  */
  623. void    machine_move()
  624. {
  625.     int            i,
  626.                 best_move;
  627.     char        best_weight;
  628.     PLAYLIST    *possibles[9];                /* Possible plays                */
  629.  
  630.     for (i = 0; i < 9; i++)
  631.         if (play_board[i] == 0)                /* If this spot is open,        */
  632.             possibles[i] = get_possible(i);    /* Get a possible for it        */
  633.         else                                /* If, however, it ain't open,    */
  634.             possibles[i] = NULL;            /* Don't keep a possible for it    */
  635.  
  636.     /*
  637.      * Go through the possibilities, looking for the highest weight.
  638.      */
  639.     best_weight = SCHAR_MIN;                /* Start with an outrageous one    */
  640.     for (i = 0; i < 9; i++)
  641.         if ((possibles[i] != NULL) &&
  642.                                     (possibles[i]->pl_weight > best_weight)) {
  643.             best_weight = possibles[i]->pl_weight;
  644.             best_move = i;
  645.         }
  646.  
  647.     play_board[best_move] = 2;
  648.     game_moves[move_count] = possibles[best_move];
  649.     move_count++;
  650.     show_move(2, best_move);
  651. }
  652.  
  653.  
  654. /*
  655.  * Ask the player if they want to do it again. If so, return 1.
  656.  * If there's a mouse, the right button means "No" and the left means "Yes".
  657.  */
  658. int        play_again()
  659. {
  660.     int        oldf,
  661.             oldb,
  662.             oldi,
  663.             oldk,
  664.             old_state;
  665.     unsigned    pinput;
  666.  
  667.     if (have_mouse)
  668.         old_state = mouse_buttons();
  669.     else
  670.         old_state = 0;
  671.     gotoxy(25, 1);
  672.     clear_eol();
  673.     get_attrib(&oldf, &oldb, &oldi, &oldk);
  674.     foreground(oldb);
  675.     background(oldf);
  676.     gotoxy(25, 34 - (strlen(yes_no_prompt) / 2));
  677.     out_string("Play again ");                /* Ask the question                */
  678.     out_string(yes_no_prompt);
  679.     out_string(" ?");
  680.     foreground(oldf);
  681.     background(oldb);
  682.  
  683.     /*
  684.      * Wait for either a keypress or mouse click.
  685.      */
  686.     for (;;) {
  687.         if ((pinput = tvinkey("YyNn", "", 0xffff)) != 0xffff)
  688.             return ((pinput == 'Y') || (pinput == 'y'));
  689.         if ((pinput = mouse_buttons()) != old_state) {
  690.             if ((old_state & 2) && !(pinput & 2))
  691.                 return (0);
  692.             if ((old_state & 1) && !(pinput & 1))
  693.                 return (1);
  694.             old_state = pinput;
  695.         }
  696.     }
  697. }
  698.  
  699.  
  700. /*
  701.  * Register a win/draw - all weights for the moves are increased by 1 for a
  702.  * draw, 2 for a win.
  703.  */
  704. void    register_win(int howmuch)
  705. {
  706.     int        i;
  707.  
  708.     for (i = 0; i < move_count; i++)
  709.         if (game_moves[i]->pl_weight <= (SCHAR_MAX - howmuch))
  710.             game_moves[i]->pl_weight += howmuch;
  711. }
  712.  
  713.  
  714. /*
  715.  * Register a loss - all weights for the moves are decreased by 2
  716.  */
  717. void    register_loss()
  718. {
  719.     int        i;
  720.  
  721.     for (i = 0; i < move_count; i++)
  722.         if (game_moves[i]->pl_weight > (SCHAR_MIN + 2))
  723.             game_moves[i]->pl_weight -= 2;
  724. }
  725.  
  726.  
  727. /*
  728.  * Display the current game stats (# played, won, lost, and drawn)
  729.  */
  730. void    show_stats()
  731. {
  732.     int        oldf,
  733.             oldb,
  734.             oldi,
  735.             oldk;
  736.     char    t[6];
  737.  
  738.     /*
  739.      * Do the legends in reverse video
  740.      */
  741.     get_attrib(&oldf, &oldb, &oldi, &oldk);
  742.     foreground(oldb);
  743.     background(oldf);
  744.     gotoxy(2, 1);
  745.     out_string("Played:");
  746.     gotoxy(4, 1);
  747.     out_string("Won:");
  748.     gotoxy(6, 1);
  749.     out_string("Lost:");
  750.     gotoxy(8, 1);
  751.     out_string("Drawn:");
  752.     games_played++;
  753.     foreground(oldf);
  754.     background(oldb);
  755.  
  756.     /*
  757.      * Now, display the numbers
  758.      */
  759.     gotoxy(2, 10);
  760.     out_string(int_asc(t, games_played, 10));
  761.     gotoxy(4, 10);
  762.     out_string(int_asc(t, games_won, 10));
  763.     gotoxy(6, 10);
  764.     out_string(int_asc(t, games_lost, 10));
  765.     gotoxy(8, 10);
  766.     out_string(int_asc(t, games_drawn, 10));
  767. }
  768.  
  769.  
  770. /*
  771.  * Play the game
  772.  */
  773. void    play_game()
  774. {
  775.     int        machine_first,
  776.             winner,
  777.             oldf,
  778.             oldb,
  779.             oldi,
  780.             oldk;
  781.  
  782.     machine_first = 0;
  783.     get_attrib(&oldf, &oldb, &oldi, &oldk);
  784.     do {
  785.         init_board();                        /* Set up the board                */
  786.         winner = 0;
  787.         if (machine_first)
  788.             machine_move();
  789.         do {
  790.             if (player_move())                /* Get move from the human    */
  791.                 break;                        /* Exit if they quit            */
  792.             if (winner = game_over())        /* If the game is over now,        */
  793.                 break;                        /*  then stop                    */
  794.             machine_move();                    /* Get a move from the machine    */
  795.         } while (!(winner = game_over()));/* Play until it's done            */
  796.         switch (winner) {
  797.             case 0:                            /* They quit                    */
  798.                 break;                        /* Do nothing                    */
  799.  
  800.             case 1:                            /* Human won                    */
  801.                 gotoxy(24, 36);
  802.                 foreground(oldb);
  803.                 background(oldf);
  804.                 out_string("You won!");
  805.                 foreground(oldf);
  806.                 background(oldb);
  807.                 register_loss();
  808.                 games_won++;
  809.                 break;
  810.  
  811.             case 2:
  812.                 gotoxy(24, 37);
  813.                 foreground(oldb);
  814.                 background(oldf);
  815.                 out_string("I won!");
  816.                 foreground(oldf);
  817.                 background(oldb);
  818.                 register_win(2);
  819.                 games_lost++;
  820.                 break;
  821.  
  822.             default:
  823.                 gotoxy(24, 38);
  824.                 foreground(oldb);
  825.                 background(oldf);
  826.                 out_string("Draw");
  827.                 foreground(oldf);
  828.                 background(oldb);
  829.                 register_win(1);
  830.                 games_drawn++;
  831.         }
  832.  
  833.         /*
  834.          * If they didn't quit, let the other one go first next time through
  835.          */
  836.         if (winner != 0)
  837.             machine_first = (machine_first == 0);
  838.         show_stats();
  839.     } while (play_again());
  840. }
  841.  
  842.  
  843. /*
  844.  * Clean up before exiting.
  845.  * Save to the memory file, clear the screen, and restore the cursor.
  846.  */
  847. void    terminate()
  848. {
  849.     FILE        *memfile;
  850.     BOARDLIST    *tboard;
  851.     PLAYLIST    *tplay;
  852.     unsigned    bcount,
  853.                 count;
  854.  
  855.     /*
  856.      * Try to create the memory file. If it fails, just die.
  857.      */
  858.     if ((memfile = fopen(memory_name, "wb")) == NULL)
  859.         err_exit("file creation", "terminate[create memory file]");
  860.  
  861.     setvbuf(memfile, NULL, _IOFBF, 8192);
  862.  
  863.     /*
  864.      * Walk the list of boards just to get a count.
  865.      */
  866.     for (bcount = 0, tboard = board_memory; tboard != NULL; tboard = tboard->bl_next)
  867.         bcount++;
  868.  
  869.     /*
  870.      * Save that count to the memory files. If it fails, (you guessed it) die.
  871.      */
  872.     if (putw(bcount, memfile) == EOF)
  873.         write_err("terminate[board count]");
  874.  
  875.     /*
  876.      * Now, walk the list to write each board out.
  877.      */
  878.     for (tboard = board_memory; tboard != NULL; tboard = tboard->bl_next)
  879.         if (fwrite(&(tboard->bl_board), 1, sizeof(tboard->bl_board) +
  880.                                                     sizeof(char), memfile) !=
  881.                                     (sizeof(tboard->bl_board) + sizeof(char)))
  882.             write_err("terminate[board list]");
  883.  
  884.     /*
  885.      * Lastly, walk the board list to write out the move lists for each one.
  886.      */
  887.     for (tboard = board_memory; tboard != NULL; tboard = tboard->bl_next) {
  888.         for (tplay = tboard->bl_plays; tplay != NULL; tplay = tplay->pl_next)
  889.             if (fwrite(&(tplay->pl_move), 1, sizeof(unsigned char) +
  890.                                                     sizeof(char), memfile) !=
  891.                                     (sizeof(unsigned char) + sizeof(char)))
  892.                 write_err("terminate[play list]");
  893.     }
  894.     fclose(memfile);
  895.     cls();
  896.     set_cursor(ON);
  897. }
  898.  
  899.  
  900. /*
  901.  * Main logic.
  902.  */
  903. main()
  904. {
  905.     initialize();                            /* Set everything up            */
  906.     play_game();                            /* Play the game                */
  907.     terminate();                            /* Clean up before exiting        */
  908.     return (0);                                /* Exit code of 0                */
  909. }
  910.